//
//  ViewController.m
//  glTextureStickers
//
//  Created by jiang on 2017/6/1.
//  Copyright © 2017年 t. All rights reserved.
//

#import "ViewController.h"
#import <GPUImage/GPUImage.h>
#import <FLAnimatedImage/FLAnimatedImage.h>
//#import "LFLiveVideoConfiguration.h"
//#import "LFGPUImageEmptyFilter.h"

@interface ViewController () <GPUImageVideoCameraDelegate>

// capture
@property (nonatomic, strong) GPUImageVideoCamera *videoCamera;
@property (nonatomic, strong) GPUImageUIElement *element;
@property (nonatomic, strong) GPUImageView *filterView;
@property (nonatomic, strong) UIView *elementView;
@property (nonatomic, strong) UIImageView *capImageView;
@property (nonatomic, assign) CGRect faceBounds;
@property (nonatomic, assign) CGPoint mouthPosition;

@property (nonatomic, strong) CIDetector *faceDetector;
//@property (nonatomic, strong) UIView *faceView;

@property (nonatomic, assign) BOOL faceThinking;

//gif
@property (nonatomic, strong, readwrite) UIImage *currentFrame;
@property (nonatomic, assign, readwrite) NSUInteger currentFrameIndex;

@property (nonatomic, assign) NSUInteger loopCountdown;
@property (nonatomic, assign) NSTimeInterval accumulator;
@property (nonatomic, strong) CADisplayLink *displayLink;

@property (nonatomic, assign) NSInteger frameInteval;

@property (nonatomic, strong) FLAnimatedImage *animatedImage;
@property (nonatomic, assign) BOOL shouldAnimate;
@property (nonatomic, assign) BOOL needsDisplayWhenImageBecomesAvailable;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    _shouldAnimate = YES;
    
    self.videoCamera = [[GPUImageVideoCamera alloc] initWithSessionPreset:AVCaptureSessionPreset1280x720 cameraPosition:AVCaptureDevicePositionFront];
    self.videoCamera.delegate = self;
    self.videoCamera.outputImageOrientation = UIInterfaceOrientationPortrait;
    self.videoCamera.horizontallyMirrorFrontFacingCamera = YES;
    
    GPUImageFilter *filter = [[GPUImageFilter alloc] init];
    [self.videoCamera addTarget:filter];
    
    self.element = [[GPUImageUIElement alloc] initWithView:self.elementView];
    
    GPUImageAlphaBlendFilter *blendFilter = [[GPUImageAlphaBlendFilter alloc] init];
    blendFilter.mix = 1.0;
    [filter addTarget:blendFilter];
    [self.element addTarget:blendFilter];
    
    self.filterView = [[GPUImageView alloc] initWithFrame:self.view.frame];
    self.filterView.center = self.view.center;
    [self.view addSubview:self.filterView];
    [blendFilter addTarget:self.filterView];
    
    __weak typeof (self) weakSelf = self;
    [filter setFrameProcessingCompletionBlock:^(GPUImageOutput *output, CMTime time) {
        __strong typeof (self) strongSelf = weakSelf;
        if (!strongSelf) {
            return;
        }
        
        // update capImageView's frame
        CGRect rect = strongSelf.faceBounds;
        CGSize size = strongSelf.capImageView.frame.size;
        strongSelf.capImageView.frame = CGRectMake(rect.origin.x +  (rect.size.width - size.width)/2, rect.origin.y - size.height, size.width, size.height);
        [strongSelf.element update];
        
        [self refreshGifImage];
    }];
    
    [self.videoCamera startCameraCapture];
    
//    LFLiveVideoConfiguration *config = [LFLiveVideoConfiguration defaultConfiguration];
//    config.autorotate = NO;
//    _capture = [[TTVideoCapture alloc] initWithVideoOrientation:config];
//    [self.capture setPreView:self.view];
//
    
    // 此处的setFrameProcessingCompletionBlock 可以模拟CADisplayLink的操作
    const NSTimeInterval kDisplayRefreshRate = 30.0; // 30Hz
    self.frameInteval = MAX([self frameDelayGreatestCommonDivisor] * kDisplayRefreshRate, 1);
    
    NSString *gifPath = [[NSBundle mainBundle] pathForResource:@"timg" ofType:@"gif"];
    NSData *gifData = [NSData dataWithContentsOfFile:gifPath];
    FLAnimatedImage *gifImage = [FLAnimatedImage animatedImageWithGIFData:gifData];
    self.animatedImage = gifImage;
//
//    _stickerImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
////    _stickerImageView.image = [UIImage imageNamed:@"floor.png"];
////    _stickerImageView.animatedImage = gifImage;
//    _stickerImageView.tag = 1;
//    
//    _waterMarkerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
//    _waterMarkerView.backgroundColor = [UIColor clearColor];
//    [_waterMarkerView addSubview:_stickerImageView];
//    self.capture.warterMarkView = self.waterMarkerView;
//    
//    self.capture.running = YES;
//    
//    [self setAnimatedImage:gifImage];
    
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark - 
#pragma mark - Getter
- (CIDetector *)faceDetector {
    if (!_faceDetector) {
        // 创建图形上下文
        CIContext * context = [CIContext contextWithOptions:nil];
        // 创建自定义参数字典
        NSDictionary * param = [NSDictionary dictionaryWithObject:CIDetectorAccuracyLow forKey:CIDetectorAccuracy];
        // 创建识别器对象
        _faceDetector = [CIDetector detectorOfType:CIDetectorTypeFace context:context options:param];
    }
    return _faceDetector;
}

- (UIView *)elementView {
    if (!_elementView) {
        _elementView = [[UIView alloc] initWithFrame:self.view.frame];
        _capImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 60, 60)];
        [_capImageView setImage:[UIImage imageNamed:@"floor.png"]];
        [_elementView addSubview:_capImageView];
    }
    return _elementView;
}

#pragma mark - GPUImageVideoCameraDelegate
- (void)willOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer {
    if (!_faceThinking) {
        CFAllocatorRef allocator = CFAllocatorGetDefault();
        CMSampleBufferRef sbufCopyOut;
        CMSampleBufferCreateCopy(allocator,sampleBuffer,&sbufCopyOut);
        [self performSelectorInBackground:@selector(grepFacesForSampleBuffer:) withObject:CFBridgingRelease(sbufCopyOut)];
    }
}

- (void)grepFacesForSampleBuffer:(CMSampleBufferRef)sampleBuffer{
    _faceThinking = YES;
    CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    CFDictionaryRef attachments = CMCopyDictionaryOfAttachments(kCFAllocatorDefault, sampleBuffer, kCMAttachmentMode_ShouldPropagate);
    CIImage *convertedImage = [[CIImage alloc] initWithCVPixelBuffer:pixelBuffer options:(__bridge NSDictionary *)attachments];
    
    if (attachments)
        CFRelease(attachments);
    NSDictionary *imageOptions = nil;
    UIDeviceOrientation curDeviceOrientation = [[UIDevice currentDevice] orientation];
    int exifOrientation;
    
    /* kCGImagePropertyOrientation values
     The intended display orientation of the image. If present, this key is a CFNumber value with the same value as defined
     by the TIFF and EXIF specifications -- see enumeration of integer constants.
     The value specified where the origin (0,0) of the image is located. If not present, a value of 1 is assumed.
     
     used when calling featuresInImage: options: The value for this key is an integer NSNumber from 1..8 as found in kCGImagePropertyOrientation.
     If present, the detection will be done based on that orientation but the coordinates in the returned features will still be based on those of the image. */
    
    enum {
        PHOTOS_EXIF_0ROW_TOP_0COL_LEFT			= 1, //   1  =  0th row is at the top, and 0th column is on the left (THE DEFAULT).
        PHOTOS_EXIF_0ROW_TOP_0COL_RIGHT			= 2, //   2  =  0th row is at the top, and 0th column is on the right.
        PHOTOS_EXIF_0ROW_BOTTOM_0COL_RIGHT      = 3, //   3  =  0th row is at the bottom, and 0th column is on the right.
        PHOTOS_EXIF_0ROW_BOTTOM_0COL_LEFT       = 4, //   4  =  0th row is at the bottom, and 0th column is on the left.
        PHOTOS_EXIF_0ROW_LEFT_0COL_TOP          = 5, //   5  =  0th row is on the left, and 0th column is the top.
        PHOTOS_EXIF_0ROW_RIGHT_0COL_TOP         = 6, //   6  =  0th row is on the right, and 0th column is the top.
        PHOTOS_EXIF_0ROW_RIGHT_0COL_BOTTOM      = 7, //   7  =  0th row is on the right, and 0th column is the bottom.
        PHOTOS_EXIF_0ROW_LEFT_0COL_BOTTOM       = 8  //   8  =  0th row is on the left, and 0th column is the bottom.
    };
    BOOL isUsingFrontFacingCamera = FALSE;
    AVCaptureDevicePosition currentCameraPosition = [self.videoCamera cameraPosition];
    
    if (currentCameraPosition != AVCaptureDevicePositionBack)
    {
        isUsingFrontFacingCamera = TRUE;
    }
    
    switch (curDeviceOrientation) {
        case UIDeviceOrientationPortraitUpsideDown:  // Device oriented vertically, home button on the top
            exifOrientation = PHOTOS_EXIF_0ROW_LEFT_0COL_BOTTOM;
            break;
        case UIDeviceOrientationLandscapeLeft:       // Device oriented horizontally, home button on the right
            if (isUsingFrontFacingCamera)
                exifOrientation = PHOTOS_EXIF_0ROW_BOTTOM_0COL_RIGHT;
            else
                exifOrientation = PHOTOS_EXIF_0ROW_TOP_0COL_LEFT;
            break;
        case UIDeviceOrientationLandscapeRight:      // Device oriented horizontally, home button on the left
            if (isUsingFrontFacingCamera)
                exifOrientation = PHOTOS_EXIF_0ROW_TOP_0COL_LEFT;
            else
                exifOrientation = PHOTOS_EXIF_0ROW_BOTTOM_0COL_RIGHT;
            break;
        case UIDeviceOrientationPortrait:            // Device oriented vertically, home button on the bottom
        default:
            exifOrientation = PHOTOS_EXIF_0ROW_RIGHT_0COL_TOP;
            break;
    }
    
    imageOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:exifOrientation] forKey:CIDetectorImageOrientation];
    NSArray *features = [self.faceDetector featuresInImage:convertedImage options:imageOptions];
    
    // get the clean aperture
    // the clean aperture is a rectangle that defines the portion of the encoded pixel dimensions
    // that represents image data valid for display.
    CMFormatDescriptionRef fdesc = CMSampleBufferGetFormatDescription(sampleBuffer);
    CGRect clap = CMVideoFormatDescriptionGetCleanAperture(fdesc, false /*originIsTopLeft == false*/);
    
    [self GPUVCWillOutputFeatures:features forClap:clap andOrientation:curDeviceOrientation];
    _faceThinking = NO;
    
}

- (void)GPUVCWillOutputFeatures:(NSArray*)featureArray forClap:(CGRect)clap
                 andOrientation:(UIDeviceOrientation)curDeviceOrientation
{
    dispatch_async(dispatch_get_main_queue(), ^{
        CGRect previewBox = self.view.frame;
        if (featureArray.count) {
            self.capImageView.hidden = NO;
        }
        else {
            self.capImageView.hidden = YES;
            //            [self.faceView removeFromSuperview];
            //            self.faceView = nil;
        }
        
        for ( CIFaceFeature *faceFeature in featureArray) {
            
            // find the correct position for the square layer within the previewLayer
            // the feature box originates in the bottom left of the video frame.
            // (Bottom right if mirroring is turned on)
            //Update face bounds for iOS Coordinate System
            CGRect faceRect = [faceFeature bounds];
            CGPoint mouthPos = faceFeature.mouthPosition;
            
            // flip preview width and height
            CGFloat temp = faceRect.size.width;
            faceRect.size.width = faceRect.size.height;
            faceRect.size.height = temp;
            temp = faceRect.origin.x;
            faceRect.origin.x = faceRect.origin.y;
            faceRect.origin.y = temp;
            // scale coordinates so they fit in the preview box, which may be scaled
            CGFloat widthScaleBy = previewBox.size.width / clap.size.height;
            CGFloat heightScaleBy = previewBox.size.height / clap.size.width;
            faceRect.size.width *= widthScaleBy;
            faceRect.size.height *= heightScaleBy;
            faceRect.origin.x *= widthScaleBy;
            faceRect.origin.y *= heightScaleBy;
            
            //mouth position
            temp = mouthPos.x;
            mouthPos.x = mouthPos.y;
            mouthPos.y = temp;
            mouthPos.x *= widthScaleBy;
            mouthPos.y *= heightScaleBy;
            
            faceRect = CGRectOffset(faceRect, previewBox.origin.x, previewBox.origin.y);
            
            //mirror
            CGRect rect = CGRectMake(previewBox.size.width - faceRect.origin.x - faceRect.size.width, faceRect.origin.y, faceRect.size.width, faceRect.size.height);
            if (fabs(rect.origin.x - self.faceBounds.origin.x) > 5.0) {
                self.faceBounds = rect;
                self.mouthPosition = mouthPos;
                
            }
        }
    });
    
}

#pragma mark - gif

- (NSTimeInterval)frameDelayGreatestCommonDivisor
{
    // Presision is set to half of the `kFLAnimatedImageDelayTimeIntervalMinimum` in order to minimize frame dropping.
    const NSTimeInterval kGreatestCommonDivisorPrecision = 2.0 / kFLAnimatedImageDelayTimeIntervalMinimum;
    
    NSArray *delays = self.animatedImage.delayTimesForIndexes.allValues;
    
    // Scales the frame delays by `kGreatestCommonDivisorPrecision`
    // then converts it to an UInteger for in order to calculate the GCD.
    NSUInteger scaledGCD = lrint([delays.firstObject floatValue] * kGreatestCommonDivisorPrecision);
    for (NSNumber *value in delays) {
        scaledGCD = gcd(lrint([value floatValue] * kGreatestCommonDivisorPrecision), scaledGCD);
    }
    
    // Reverse to scale to get the value back into seconds.
    return scaledGCD / kGreatestCommonDivisorPrecision;
}


static NSUInteger gcd(NSUInteger a, NSUInteger b)
{
    // http://en.wikipedia.org/wiki/Greatest_common_divisor
    if (a < b) {
        return gcd(b, a);
    } else if (a == b) {
        return b;
    }
    
    while (true) {
        NSUInteger remainder = a % b;
        if (remainder == 0) {
            return b;
        }
        a = b;
        b = remainder;
    }
}

//- (void)displayDidRefresh:(CADisplayLink *)displayLink
//{
//    // If for some reason a wild call makes it through when we shouldn't be animating, bail.
//    // Early return!
//    if (!self.shouldAnimate) {
//        FLLog(FLLogLevelWarn, @"Trying to animate image when we shouldn't: %@", self);
//        return;
//    }
//    
//    NSNumber *delayTimeNumber = [self.animatedImage.delayTimesForIndexes objectForKey:@(self.currentFrameIndex)];
//    // If we don't have a frame delay (e.g. corrupt frame), don't update the view but skip the playhead to the next frame (in else-block).
//    if (delayTimeNumber) {
//        NSTimeInterval delayTime = [delayTimeNumber floatValue];
//        // If we have a nil image (e.g. waiting for frame), don't update the view nor playhead.
//        UIImage *image = [self.animatedImage imageLazilyCachedAtIndex:self.currentFrameIndex];
//        if (image) {
//            FLLog(FLLogLevelVerbose, @"Showing frame %lu for animated image: %@", (unsigned long)self.currentFrameIndex, self.animatedImage);
//            self.currentFrame = image;
//            if (self.needsDisplayWhenImageBecomesAvailable) {
////                [self.layer setNeedsDisplay];
//                
//                //update image frame
////                _stickerImageView.image = self.currentFrame;
//                
//                self.needsDisplayWhenImageBecomesAvailable = NO;
//            }
//            
//            self.accumulator += displayLink.duration * displayLink.frameInterval;
//            
//            // While-loop first inspired by & good Karma to: https://github.com/ondalabs/OLImageView/blob/master/OLImageView.m
//            while (self.accumulator >= delayTime) {
//                self.accumulator -= delayTime;
//                self.currentFrameIndex++;
//                if (self.currentFrameIndex >= self.animatedImage.frameCount) {
//                    // If we've looped the number of times that this animated image describes, stop looping.
//                    self.loopCountdown--;
////                    if (self.loopCompletionBlock) {
////                        self.loopCompletionBlock(self.loopCountdown);
////                    }
//                    
//                    if (self.loopCountdown == 0) {
////                        [self stopAnimating];
//                        return;
//                    }
//                    self.currentFrameIndex = 0;
//                }
//                // Calling `-setNeedsDisplay` will just paint the current frame, not the new frame that we may have moved to.
//                // Instead, set `needsDisplayWhenImageBecomesAvailable` to `YES` -- this will paint the new image once loaded.
//                self.needsDisplayWhenImageBecomesAvailable = YES;
//            }
//        } else {
////            FLLog(FLLogLevelDebug, @"Waiting for frame %lu for animated image: %@", (unsigned long)self.currentFrameIndex, self.animatedImage);
////#if defined(DEBUG) && DEBUG
////            if ([self.debug_delegate respondsToSelector:@selector(debug_animatedImageView:waitingForFrame:duration:)]) {
////                [self.debug_delegate debug_animatedImageView:self waitingForFrame:self.currentFrameIndex duration:(NSTimeInterval)displayLink.duration * displayLink.frameInterval];
////            }
////#endif
//        }
//    } else {
//        self.currentFrameIndex++;
//    }
//}

- (void)refreshGifImage {
    // If for some reason a wild call makes it through when we shouldn't be animating, bail.
    // Early return!
    if (!self.shouldAnimate) {
        FLLog(FLLogLevelWarn, @"Trying to animate image when we shouldn't: %@", self);
        return;
    }
    
    NSNumber *delayTimeNumber = [self.animatedImage.delayTimesForIndexes objectForKey:@(self.currentFrameIndex)];
    // If we don't have a frame delay (e.g. corrupt frame), don't update the view but skip the playhead to the next frame (in else-block).
    if (delayTimeNumber) {
        NSTimeInterval delayTime = [delayTimeNumber floatValue];
        // If we have a nil image (e.g. waiting for frame), don't update the view nor playhead.
        UIImage *image = [self.animatedImage imageLazilyCachedAtIndex:self.currentFrameIndex];
        if (image) {
            FLLog(FLLogLevelVerbose, @"Showing frame %lu for animated image: %@", (unsigned long)self.currentFrameIndex, self.animatedImage);
            self.currentFrame = image;
            if (self.needsDisplayWhenImageBecomesAvailable) {
                //                [self.layer setNeedsDisplay];
                
                //update image frame
                //                _stickerImageView.image = self.currentFrame;
                self.capImageView.image = self.currentFrame;
                self.needsDisplayWhenImageBecomesAvailable = NO;
            }
            
            self.accumulator += 0.033f * self.frameInteval;
            
            // While-loop first inspired by & good Karma to: https://github.com/ondalabs/OLImageView/blob/master/OLImageView.m
            while (self.accumulator >= delayTime) {
                self.accumulator -= delayTime;
                self.currentFrameIndex++;
                if (self.currentFrameIndex >= self.animatedImage.frameCount) {
                    // If we've looped the number of times that this animated image describes, stop looping.
                    self.loopCountdown--;
                    //                    if (self.loopCompletionBlock) {
                    //                        self.loopCompletionBlock(self.loopCountdown);
                    //                    }
                    
                    if (self.loopCountdown == 0) {
                        //                        [self stopAnimating];
                        return;
                    }
                    self.currentFrameIndex = 0;
                }
                // Calling `-setNeedsDisplay` will just paint the current frame, not the new frame that we may have moved to.
                // Instead, set `needsDisplayWhenImageBecomesAvailable` to `YES` -- this will paint the new image once loaded.
                self.needsDisplayWhenImageBecomesAvailable = YES;
            }
        } else {
            //            FLLog(FLLogLevelDebug, @"Waiting for frame %lu for animated image: %@", (unsigned long)self.currentFrameIndex, self.animatedImage);
            //#if defined(DEBUG) && DEBUG
            //            if ([self.debug_delegate respondsToSelector:@selector(debug_animatedImageView:waitingForFrame:duration:)]) {
            //                [self.debug_delegate debug_animatedImageView:self waitingForFrame:self.currentFrameIndex duration:(NSTimeInterval)displayLink.duration * displayLink.frameInterval];
            //            }
            //#endif
        }
    } else {
        self.currentFrameIndex++;
    }
}


@end
